package com.element.sentinel.init;

import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
import com.alibaba.csp.sentinel.util.HostNameUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.api.PropertyKeyConst;

import java.util.*;

/**
 * 初始化集群限流
 */
public class InitClusterFunc implements InitFunc {

    private static final String GROUP_ID = "SENTINEL_GROUP";
    private static final String FLOW_POSTFIX = "-flow-rules";
    private static final String PARAM_FLOW_POSTFIX = "-param-rules";
    private static final String CLUSTER_MAP_POSTFIX = "-cluster-map";
    private static final String CLIENT_CONFIG_POSTFIX = "-cc-config";

    private static final String APP_NAME = "csp.sentinel.app.name";
    private static final String SENTINEL_DATASOURCE_URL = "sentinel.datasource.url";
    private static final String SENTINEL_DATASOURCE_NAMESPACE = "sentinel.datasource.namespace";

    @Override
    public void init() {
        Properties propertiesList = System.getProperties();
        String appName = propertiesList.getProperty(APP_NAME);
        String url = propertiesList.getProperty(SENTINEL_DATASOURCE_URL);
        String namespace = propertiesList.getProperty(SENTINEL_DATASOURCE_NAMESPACE);
        if (StringUtil.isNotBlank(appName) && StringUtil.isNotBlank(url) && StringUtil.isNotBlank(namespace)) {
            Properties properties = new Properties();
            // 使用namespace的方式加载Nacos配置
            properties.put(PropertyKeyConst.SERVER_ADDR, url);
            properties.put(PropertyKeyConst.NAMESPACE, namespace);

            initClientConfigProperty(appName, properties);
            // client:加载ClusterClientAssignConfig(serverHost、serverPort)
            initClientServerAssignProperty(appName, properties);
            // 加载线上规则
            registerClusterRuleSupplier(properties);
            // server:从assignMap中获取ServerTransportConfig(port、idleSeconds)
            initServerTransportConfigProperty(appName, properties);
            // 根据我们的clusterDemo-cluster-map配置，设置当前应用状态（CLIENT/SERVER/NOT_STARTED）
            initStateProperty(appName, properties);
        }
    }

    private void initClientConfigProperty(String appName, Properties properties) {
        ReadableDataSource<String, ClusterClientConfig> clientConfigDs = new NacosDataSource<>(properties, GROUP_ID,
                (appName + CLIENT_CONFIG_POSTFIX), source -> {
            Map<String, ClusterClientConfig> map = JSON.parseObject(source, new TypeReference<Map<String, ClusterClientConfig>>() {
            });
            ClusterClientConfig config = map.get(getCurrentMachineId());
            if (null == config) {
                config = new ClusterClientConfig();
                config.setRequestTimeout(2000);
            }
            return config;
        });
        ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty());
    }

    // 这里主要是通过map配置项中的clientSet,比对当前应用的ip:port来确认当前是否client端,如果是,则设置serverIp:serverPort为配置中的ip:port
    private void initClientServerAssignProperty(String appName, Properties properties) {
        ReadableDataSource<String, ClusterClientAssignConfig> clientAssignDs = new NacosDataSource<>(properties, GROUP_ID,
                (appName + CLUSTER_MAP_POSTFIX), source -> {
            List<ClusterGroupEntity> groupList = JSON.parseObject(source, new TypeReference<List<ClusterGroupEntity>>() {
            });
            return Optional.ofNullable(groupList)
                    // 主要在这里
                    .flatMap(this::extractClientAssignment)
                    .orElse(null);
        });
        ClusterClientConfigManager.registerServerAssignProperty(clientAssignDs.getProperty());
    }

    private void registerClusterRuleSupplier(Properties properties) {
        ClusterFlowRuleManager.setPropertySupplier(appName -> {
            ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(properties, GROUP_ID,
                    (appName + FLOW_POSTFIX), source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
            }));
            return ds.getProperty();
        });
        ClusterParamFlowRuleManager.setPropertySupplier(appName -> {
            ReadableDataSource<String, List<ParamFlowRule>> ds = new NacosDataSource<>(properties, GROUP_ID,
                    (appName + PARAM_FLOW_POSTFIX), source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {
            }));
            return ds.getProperty();
        });
    }

    // server端加载port
    private void initServerTransportConfigProperty(String appName, Properties properties) {
        ReadableDataSource<String, ServerTransportConfig> serverTransportDs = new NacosDataSource<>(properties, GROUP_ID,
                (appName + CLUSTER_MAP_POSTFIX), source -> {
            List<ClusterGroupEntity> groupList = JSON.parseObject(source, new TypeReference<List<ClusterGroupEntity>>() {
            });
            return Optional.ofNullable(groupList)
                    // 主要在这里，通过clusterDemo-cluster-map配置的值中的machineID来比对当前应用IP:port是否符合，符合则代表是server端
                    // 获取配置中的port值
                    .flatMap(this::extractServerTransportConfig)
                    .orElse(null);
        });
        ClusterServerConfigManager.registerServerTransportProperty(serverTransportDs.getProperty());
    }

    // 这里同样很关键，通过map配置中提前设定好的clientSet，machineID来确定当前应用是server还是client
    private void initStateProperty(String appName, Properties properties) {
        ReadableDataSource<String, Integer> clusterModeDs = new NacosDataSource<>(properties, GROUP_ID,
                (appName + CLUSTER_MAP_POSTFIX), source -> {
            List<ClusterGroupEntity> groupList = JSON.parseObject(source, new TypeReference<List<ClusterGroupEntity>>() {
            });
            return Optional.ofNullable(groupList)
                    // 主要在这里
                    .map(this::extractMode)
                    .orElse(ClusterStateManager.CLUSTER_NOT_STARTED);
        });
        ClusterStateManager.registerProperty(clusterModeDs.getProperty());
    }

    private Optional<ClusterClientAssignConfig> extractClientAssignment(List<ClusterGroupEntity> groupList) {
        if (groupList.stream().anyMatch(this::machineEqual)) {
            return Optional.empty();
        }
        for (ClusterGroupEntity group : groupList) {
            if (group.getClientSet().contains(getCurrentMachineId())) {
                String ip = group.getIp();
                Integer port = group.getPort();
                return Optional.of(new ClusterClientAssignConfig(ip, port));
            }
        }
        return Optional.empty();
    }

    private Optional<ServerTransportConfig> extractServerTransportConfig(List<ClusterGroupEntity> groupList) {
        return groupList.stream()
                .filter(this::machineEqual)
                .findAny()
                .map(e -> new ServerTransportConfig().setPort(e.getPort()).setIdleSeconds(600));
    }

    private int extractMode(List<ClusterGroupEntity> groupList) {
        if (groupList.stream().anyMatch(this::machineEqual)) {
            return ClusterStateManager.CLUSTER_SERVER;
        }
        boolean canBeClient = groupList.stream()
                .flatMap(e -> e.getClientSet().stream())
                .filter(Objects::nonNull)
                .anyMatch(e -> e.equals(getCurrentMachineId()));
        return canBeClient ? ClusterStateManager.CLUSTER_CLIENT : ClusterStateManager.CLUSTER_NOT_STARTED;
    }

    private boolean machineEqual(ClusterGroupEntity group) {
        return getCurrentMachineId().equals(group.getMachineId());
    }

    private String getCurrentMachineId() {
        return HostNameUtil.getIp() + SEPARATOR + TransportConfig.getPort();
    }

    private static final String SEPARATOR = "@";
}